package repository

import (
	"context"
	"database/sql"

	"gitee.com/hp-huiw/family-expense-api/internal/db"
	dbmodel "gitee.com/hp-huiw/family-expense-api/internal/db/model"
	jet "github.com/go-jet/jet/v2/mysql"
)

type UserRoleRepository interface {
	QueryRoleIDs(ctx context.Context, accountGroupID string, userID string) ([]string, error)
	QueryFullRoles(ctx context.Context, accountGroupID string, userID string) ([]Role, error)
	Exists(ctx context.Context, accountGroupID string, userID string, roleName string) (bool, error)
	InsertWithTx(ctx context.Context, tx *sql.Tx, model *dbmodel.UserRole) error
	BatchInsertWithTx(ctx context.Context, tx *sql.Tx, models []dbmodel.UserRole) error
	DeleteInOrWithTx(ctx context.Context, tx *sql.Tx, accountGroupID string, userID string) error
	DeleteInAndWithTx(ctx context.Context, tx *sql.Tx, accountGroupID string, userID string) error
	DeleteWithTx(ctx context.Context, tx *sql.Tx, accountGroupID string, userID string, roleIDs []string) error
}

func NewUserRoleRepository(db *sql.DB) UserRoleRepository {
	return &userRoleRepository{db: db}
}

func (r *userRoleRepository) QueryRoleIDs(ctx context.Context, accountGroupID string, userID string) ([]string, error) {
	var query []string

	stmt := jet.
		SELECT(
			tbUserRole.RoleID,
		).
		FROM(
			tbUserRole,
		).
		WHERE(
			tbUserRole.AccountGroupID.EQ(jet.String(accountGroupID)).
				AND(tbUserRole.UserID.EQ(jet.String(userID))),
		)

	err := stmt.QueryContext(ctx, r.db, &query)

	if err != nil {
		return nil, err
	}

	return query, nil
}

func (r *userRoleRepository) QueryFullRoles(ctx context.Context, accountGroupID string, userID string) ([]Role, error) {
	var query []struct {
		ID          string  `db:"id"`
		Title       string  `db:"title"`
		Authority   *string `db:"authority"`
		Description *string `db:"description"`
	}

	stmt := jet.
		SELECT(
			tbRole.ID.AS("id"),
			tbRole.Title.AS("title"),
			tbRole.Authority.AS("authority"),
		).
		FROM(
			tbUserRole.
				INNER_JOIN(tbRole, tbRole.ID.EQ(tbUserRole.RoleID)),
		).
		WHERE(
			tbUserRole.AccountGroupID.EQ(jet.String(accountGroupID)).
				AND(tbUserRole.UserID.EQ(jet.String(userID))),
		)

	if err := stmt.QueryContext(ctx, r.db, &query); err != nil {
		return nil, err
	}

	result := make([]Role, 0, len(query))

	for _, item := range query {
		result = append(result, item)
	}

	return result, nil
}

func (r *userRoleRepository) Exists(ctx context.Context, accountGroupID string, userID string, roleName string) (bool, error) {
	from := tbUserRole.INNER_JOIN(tbRole, tbUserRole.RoleID.EQ(tbRole.ID))

	condition := tbUserRole.AccountGroupID.EQ(jet.String(accountGroupID)).
		AND(tbUserRole.UserID.EQ(jet.String(userID))).
		AND(tbRole.Authority.EQ(jet.String(roleName)))

	return db.Exists(ctx, r.db, from, condition)
}

// InsertWithTx inserts user role with transaction.
func (*userRoleRepository) InsertWithTx(ctx context.Context, tx *sql.Tx, model *dbmodel.UserRole) error {
	stmt := tbUserRole.
		INSERT().
		MODEL(model)

	_, err := stmt.ExecContext(ctx, tx)

	return err
}

// BatchInsertWithTx inserts user roles with transaction.
func (*userRoleRepository) BatchInsertWithTx(ctx context.Context, tx *sql.Tx, models []dbmodel.UserRole) error {
	stmt := tbUserRole.
		INSERT().
		MODELS(models)

	_, err := stmt.ExecContext(ctx, tx)

	return err
}

// DeleteInOrWithTx deletes all user roles by account group id or user id with transaction.
func (*userRoleRepository) DeleteInOrWithTx(ctx context.Context, tx *sql.Tx, accountGroupID string, userID string) error {
	stmt := tbUserRole.
		DELETE().
		WHERE(
			tbUserRole.AccountGroupID.EQ(jet.String(accountGroupID)).
				OR(tbUserRole.UserID.EQ(jet.String(userID))),
		)

	_, err := stmt.ExecContext(ctx, tx)

	return err
}

// DeleteInAndWithTx deletes all user roles by account group id and user id with transaction.
func (r *userRoleRepository) DeleteInAndWithTx(ctx context.Context, tx *sql.Tx, accountGroupID string, userID string) error {
	return r.deleteByUserAndAccountGroupAndIDsWithTx(ctx, tx, accountGroupID, userID, nil)
}

func (r *userRoleRepository) DeleteWithTx(ctx context.Context, tx *sql.Tx, accountGroupID string, userID string, roleIDs []string) error {
	return r.deleteByUserAndAccountGroupAndIDsWithTx(ctx, tx, accountGroupID, userID, roleIDs)
}

func (*userRoleRepository) deleteByUserAndAccountGroupAndIDsWithTx(ctx context.Context, tx *sql.Tx, accountGroupID string, userID string, roleIDs []string) error {
	condition := tbUserRole.AccountGroupID.EQ(jet.String(accountGroupID)).
		AND(tbUserRole.UserID.EQ(jet.String(userID)))

	if len(roleIDs) > 0 {
		roleIDsExpression := db.SliceExpression(roleIDs)
		condition = condition.AND(tbUserRole.RoleID.IN(roleIDsExpression...))
	}

	stmt := tbUserRole.
		DELETE().
		WHERE(condition)

	_, err := stmt.ExecContext(ctx, tx)

	return err
}

type userRoleRepository struct {
	db *sql.DB
}
